/*
 * ml20gui - The MSN Webcam Recorder
 * This source code is under the GNU Public License (GPL).
 * Written by Ramiro Polla
 * Initial release: june 3rd, 2005
 *
 * this program records MSN Webcam Video
 * this file had many parts copied from tcpflow,
 * as most of the recording part of the program has.
 */

#include "ml20gui.h"

#define HASH_SIZE   1543

#define HASH_FLOW(flow) ( \
( (flow->sport & 0xff) | ((flow->dport & 0xff) << 8) | \
  ((flow->src & 0xff) << 16) | ((flow->dst & 0xff) << 24) \
) % HASH_SIZE)

static stream_t *flow_hash[HASH_SIZE];

static invite_t *first_invite;
static invite_t *last_invite;

stream_t *identify_flow( uint32_t src, uint32_t dst, uint16_t sport, uint16_t dport, const uint8_t *data, uint32_t length )
{
    stream_t *state;
    flow_t t_flow;
    int i;

    t_flow.src = src;
    t_flow.dst = dst;
    t_flow.sport = sport;
    t_flow.dport = dport;

    /* see if we have state about this flow; if not, create it */
    if ( !(state = find_stream(&t_flow)) ) {
        flow_t *this_flow = calloc( 1, sizeof(flow_t) );
        memcpy( this_flow, &t_flow, sizeof(flow_t) );
        state = create_stream(this_flow);
    }

    if( !IS_SET( state->flags, FLOW_ML20 ) &&
        !IS_SET( state->flags, FLOW_POSSIBLE_VC ) &&
        !IS_SET( state->flags, FLOW_VC ) )
    {
        i = look_for_stuff( (unsigned char *) data, length, state->flow );

        if( i != (-1) ) {
            SET_BIT( state->flags, FLOW_ML20 );
            SET_BIT( state->flags, FLOW_NEW );
            //set the initial data to where the first ml20 header was found
            state->offset = i;
        } else {
            if( is_vc( (unsigned char*) data, length, TRUE ) ) {
                //found something that might be a Video Conversation.
                SET_BIT( state->flags, FLOW_POSSIBLE_VC );
            }
        }
    }

    if( IS_SET( state->flags, FLOW_POSSIBLE_VC ) ) {
        if( state->pvc.ok + state->pvc.notok >= 20 ) {
            // 80%
            if( state->pvc.ok >= 16 ) {
                // Video Conversation
                SET_BIT( state->flags, FLOW_VC );
                SET_BIT( state->flags, FLOW_NEW );
            } else {
                // NOT Video Conversation
                free( state->pvc.data );
                memset( &state->pvc, 0, sizeof(state->pvc) );
            }
            UNSET_BIT( state->flags, FLOW_POSSIBLE_VC );
        } else {
            int f_is_vc;
            if( (f_is_vc = is_vc( (unsigned char*) data, length, FALSE )) )
                state->pvc.ok++;
            else
                state->pvc.notok++;

            // Even if is_vc returned FALSE, write up to what we can.
            // The error might have been in a packet beyond the first.
            {
                int32_t slength = length;
                vcinthdr *vc;

                while( slength != 0 ) {
                    int totlength;
                    if( slength < 10 ) //minimum for a header
                        break;

                    vc = (vcinthdr*) data;
                    if( ( vc->size + 10 ) > slength )
                        break;

                    totlength = state->pvc.length + ( vc->size + 10 );
                    state->pvc.data = realloc( state->pvc.data, totlength );
                    memcpy( state->pvc.data + state->pvc.length, data, ( vc->size + 10 ) );
                    state->pvc.length = totlength;

                    slength -= ( vc->size + 10 );
                    data += ( vc->size + 10 );
                }
            }
#if 0
            else {
                // Make a fake vcinthdr with the UDP packet.
                // I prefer to record everything and just not
                // use what I don't want.
                int totlength = state->pvc.length + length + 10;
                vcinthdr fake = { 0x01, 0, 0, 0, 0, 0, 0, 0 };
                fake.size = length;
                state->pvc.data = realloc( state->pvc.data, totlength );
                memcpy( state->pvc.data + state->pvc.length, &fake, 10 );
                memcpy( state->pvc.data + state->pvc.length + 10, data, length );
                state->pvc.length = totlength;
            }
#endif
        }
    }

    return state;
}

void init_streams()
{
    memset( flow_hash, 0x00, sizeof(flow_hash) );

    first_invite = calloc( 1, sizeof( invite_t ) );
    last_invite = first_invite;
}

stream_t *create_stream( flow_t *flow )
{
    stream_t *new_flow = (stream_t *) calloc( 1, sizeof( stream_t ) );

    int index = HASH_FLOW(flow);
    new_flow->next = flow_hash[index];
    flow_hash[index] = new_flow;

    /* initialize contents of the state structure */
    // anything that must be set to zero is already set.

    new_flow->flow = flow;
    time( &new_flow->start_time );
    SET_BIT( new_flow->flags, FLOW_LIVE );

    return new_flow;
}

typedef struct {
    uint32_t dwords[3];
} threedwords;

stream_t *find_stream(flow_t *flow)
{
    stream_t *ptr;
    int index = HASH_FLOW(flow);
    threedwords *tdw = (threedwords *) flow;
    register uint32_t dword1, dword2, dword3;

    dword1 = tdw->dwords[0];
    dword2 = tdw->dwords[1];
    dword3 = tdw->dwords[2];

    // well, this certainly is ugly code, but it's very well
    // optimized. It works as a memcmp for 2 dwords and a 2 words.
    for ( ptr = flow_hash[index]; ptr ; ptr = ptr->next ) {
        threedwords *tdw2 = (threedwords *) ptr->flow;
        if( tdw2->dwords[0] == dword1 &&
            tdw2->dwords[1] == dword2 &&
            tdw2->dwords[2] == dword3 )
        {
            ptr->last_time = GetTickCount( );
            break;
        }
    }

    return ptr;
}

void close_flow_files( )
{
    stream_t *ptr;
    int index;

    for( index = 0 ; index < HASH_SIZE ; index++ )
        for ( ptr = flow_hash[index]; ptr ; ptr = ptr->next )
            close_file( ptr );
}

invite_t *create_invite( flow_t *flow )
{
    invite_t *new_invite;

    new_invite = last_invite;
    last_invite = (invite_t *) calloc( 1, sizeof( invite_t ) );
    new_invite->next = last_invite;
    last_invite->previous = new_invite;

    /* initialize contents of the state structure */
    // switch dst for src
    new_invite->flow.dport  = flow->sport;
    new_invite->flow.dst    = flow->src;
    new_invite->flow.sport  = flow->dport;
    new_invite->flow.src    = flow->dst;

    return new_invite;
}

invite_t *find_invite(flow_t *flow)
{
    invite_t *ptr;

    for ( ptr = last_invite->previous; ptr ; ptr = ptr->previous )
        if (!memcmp((char *) flow, (char *) (&ptr->flow), sizeof(flow_t)))
            return ptr;

    return NULL;
}

invite_t *find_invite_ip(flow_t *flow, int type)
{
    invite_t *ptr;
    ipp_t *ipp;

    for( ptr = last_invite->previous ; ptr ; ptr = ptr->previous )
        for( ipp = ptr->ipp ; ipp ; ipp = ipp->next )
            if ( ipp->ip == (IS_SET( type, TYPE_SRC) ? flow->src : flow->dst) )
                return ptr;

    return NULL;
}
